﻿using System;
using System.Linq;
using System.Collections.Generic;
using CI.Interfaces;
using CI.Organism;
using CI.PW.Models;
using QuickGraph;

namespace CI.PW.Controls
{
    /// <summary>
    /// Builds graphs and draws them in the nnet
    /// </summary>
    public static class NeuralNetworkDraw
    {
        /// <summary>
        /// Build a graph from the organism supplied
        /// </summary>
        /// <param name="organism"></param>
        /// <returns></returns>
        public static BidirectionalGraph<object, IEdge<object>> BuildGraph(IOrganism organism)
        {
            #region Argument Checker
            if (organism == null)
                throw new ArgumentException("organism");
            #endregion

            // create a biderecitonal graph
            var graph = new BidirectionalGraph<object, IEdge<object>>();

            var neurons = new List<INeuron>(organism.NeuralSystem.Neurons);
            var synapseIOs = new List<SynapseIO>(organism.GetIOManifest());

            var vertexModels = new List<VertexModel>();

            if (synapseIOs.Count == 0)
                SimEnv.Instance.TraceLogger.LogWarn("No synapseIOs found for " + organism.Zygote());

            // add each neuron(vertex)
            foreach (INeuron neuron in neurons)
                vertexModels.Add(new VertexModel(neuron));

            // add each IO Synapse(vertex)
            foreach (SynapseIO synapseIO in synapseIOs)
                vertexModels.Add(new VertexModel(synapseIO, organism.NeuralSystem));

            graph.AddVertexRange(vertexModels.ToArray());

            AddSynapsesToGraph(graph, organism.NeuralSystem.Synapses, vertexModels);

            if (graph.EdgeCount > 0)
                RemoveUnusedNodes(graph);
            else
                graph.Clear();

            return graph;
        }

        /// <summary>
        /// if the nodes arent connected to any edges remove them
        /// </summary>
        /// <param name="graph"></param>
        private static void RemoveUnusedNodes(BidirectionalGraph<object, IEdge<object>> graph)
        {
            foreach (var vertex in graph.Vertices.ToList())
            {
                bool connectedEdge = false;

                foreach (var edge in graph.Edges)
                {
                    if (vertex is SynapseIO)
                    {
                        if ((edge.Source.ToString() == vertex.ToString()) ||
                            (edge.Target.ToString() == vertex.ToString()))
                        {
                            connectedEdge = true;
                            break;
                        }
                    }

                    if ((edge.Source == vertex) || (edge.Target == vertex))
                    {
                        connectedEdge = true;
                        break;
                    }
                }

                if (!connectedEdge)
                {
                    graph.RemoveVertex(vertex);
                }
            }
        }

        /// <summary>
        /// Addeds synapses as edges to the graph
        /// </summary>
        /// <param name="graph"></param>
        /// <param name="synapses"></param>
        /// <param name="vertexModels"></param>
        private static void AddSynapsesToGraph(BidirectionalGraph<object, IEdge<object>> graph, IEnumerable<ISynapse> synapses, IEnumerable<VertexModel> vertexModels)
        {
            foreach (var synapse in synapses.ToList())
            {
                if (synapse.SourceNotification == null || synapse.TargetNotification == null)
                    continue;

                object source = vertexModels.FirstOrDefault(v => synapse.SourceNotification.Equals(v.Neuron));
                object target = vertexModels.FirstOrDefault(v => synapse.TargetNotification.Equals(v.Neuron));

                if (source == null)
                    source = vertexModels.FirstOrDefault(s => synapse.SourceNotification.Equals(s.SynapseIO));

                if (target == null)
                    target = vertexModels.FirstOrDefault(s => synapse.TargetNotification.Equals(s.SynapseIO));

                if ((source != null) && (target != null) && (source != target))
                {
                    if (graph.ContainsVertex(source) && graph.ContainsVertex(target))
                        graph.AddEdge(new EdgeSynapse(source, target));
                }
            }
        }

    }
}


